const path = require("path");
const https = require("https");
const fs = require("fs");
const commander = require("commander");
const cheerio = require("cheerio");
const puppeteer = require("puppeteer");
const semver = require("semver");
const chalk = require("chalk");

const packageJson = require("../package.json");
const evaluatePage = require("./evaluatePage");
const { Spinner, stringify, log, getType } = require("./utils");

const originalDirectory = process.cwd();

function init() {
  let skeletonUrl;
  const program = new commander.Command(packageJson.name)
    .version(packageJson.version)
    .arguments("<create-skeleton-url>")
    .description("input skeleton screen url")
    .action(url => {
      skeletonUrl = url;
    })
    .allowUnknownOption()
    .on("--help", () => {
      console.log(
        `如果未配置文件: npx  ${chalk.cyan(program.name())} ${chalk.green(
          "https://baidu.com"
        )}`
      );
      console.log(`如果已配置文件: npx ${chalk.cyan(program.name())}`);
    })
    .parse(process.argv);

  const skeletonConfigPath = path.resolve(
    originalDirectory,
    `${packageJson.name}.config.js`
  );

  const existSkeletonConfig = fs.existsSync(skeletonConfigPath);

  if (typeof skeletonUrl === "undefined") {
    if (!existSkeletonConfig) {
      console.error("Please specify the skeleton url:");
      console.log(
        `  ${chalk.cyan(program.name())} ${chalk.green(
          "<create-skeleton-url>"
        )}`
      );
      console.log();
      console.log("For example:");
      console.log(
        `  ${chalk.cyan(program.name())} ${chalk.green("https://baidu.com")}`
      );
      console.log();
      console.log(
        `Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
      );
      process.exit(1);
    }
  }

  checkForLatestVersion()
    .catch(() => {
      try {
        return execSync(`npm view ${packageJson.name} version`)
          .toString()
          .trim();
      } catch (e) {
        return null;
      }
    })
    .then(latest => {
      if (latest && semver.lt(packageJson.version, latest)) {
        console.log();
        console.error(
          chalk.yellow(
            `You are running \`${packageJson.name}\` ${packageJson.version}, which is behind the latest release (${latest}).\n\n` +
              `We no longer support global installation of ${packageJson.name}.`
          )
        );
        console.log();
        console.log(
          "Please remove any global installs with one of the following commands:\n" +
            `- npm uninstall -g ${packageJson.name}\n` +
            `- yarn global remove ${packageJson.name}`
        );
        console.log();
        console.log(
          "The latest instructions for creating a skeleton can be found here:\n" +
            packageJson.homepage
        );
        console.log();
        process.exit(1);
      } else {
        let skeletonConfig = {};
        if (existSkeletonConfig) {
          skeletonConfig = require(skeletonConfigPath);
        }
        createSkeleton({
          url: skeletonUrl,
          devtools: true,
          headless: true,
          ...skeletonConfig
        });
      }
    });
}

function checkForLatestVersion(packageName) {
  return new Promise((resolve, reject) => {
    https
      .get(
        `https://registry.npmjs.org/-/package/${packageName ||
          packageJson.name}/dist-tags`,
        res => {
          if (res.statusCode === 200) {
            let body = "";
            res.on("data", data => (body += data));
            res.on("end", () => {
              resolve(JSON.parse(body || "{}").latest);
            });
          } else {
            reject();
          }
        }
      )
      .on("error", () => {
        reject();
      });
  });
}

async function puppet(device = "iPhone 6", launchOption) {
  const currentDevice = puppeteer.devices[device];
  const browser = await puppeteer.launch(launchOption);

  async function openPage(url, extraHTTPHeaders) {
    const page = await browser.newPage();
    try {
      await page.emulate(currentDevice);
      if (extraHTTPHeaders && getType(extraHTTPHeaders) === "object") {
        await page.setExtraHTTPHeaders(
          new Map(Object.entries(extraHTTPHeaders))
        );
      }
      await page.goto(url, {
        timeout: 2 * 60 * 1000,
        waitUntil: "networkidle0"
      });
    } catch (e) {
      console.log("\n");
      log.error(e.message);
    }
    return page;
  }
  return {
    browser,
    openPage
  };
}

async function createSkeleton({
  url = "",
  output: { filePath = "", injectSelector = "" } = {},
  device,
  headless = false,
  devtools = true,
  extraHTTPHeaders,
  background = "#ecf0f2",
  animation = "",
  rootNode = "",
  header = "",
  beforeDraw,
  includeNode,
  launchOption = {},
  writeSkeleton
}) {
  const spinner = Spinner("magentaBright");
  spinner.text = "正在启动浏览器...";

  const { browser, openPage } = await puppet(device, {
    headless,
    devtools,
    ...launchOption
  });

  spinner.text = `正在打开页面：${url}...`;
  const page = await openPage(url, extraHTTPHeaders);

  spinner.text = "正在生成骨架屏...";
  const pageEvaluateArgs = stringify({
    beforeDraw,
    header,
    rootNode,
    animation,
    background,
    includeNode,
    devtools,
    headless
  });

  const content = await new Promise(resolve => {
    let currentPage;

    browser.on("targetchanged", async () => {
      const targets = await browser.targets();
      const currentTarget = targets[targets.length - 1];
      currentPage = await currentTarget.page();
      resolve(await page.evaluate(evaluatePage, pageEvaluateArgs));
    });

    page.evaluate(evaluatePage, pageEvaluateArgs).then(value => {
      if (!currentPage) {
        resolve(value);
      }
    });
  });

  if (writeSkeleton) {
    writeSkeleton(content);
  } else {
    writeSkeletonToFile({ filePath, content, injectSelector });
  }

  spinner
    .clear()
    .succeed(
      `skeleton screen has created and output to ${filePath ||
        path.join(process.cwd(), `/${packageJson.name}.html`)}`
    );

  if (headless) {
    await browser.close();
    process.exit(0);
  }
}

function writeSkeletonToFile({ filePath, content, injectSelector }) {
  const data = `<!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width,initial-scale=1.0">
      <title>example</title>
      <style>
        @keyframes ant-skeleton-loading {
          0% {
            background-position: 100% 50%;
          }
          100% {
            background-position: 0 50%;
          }
        }
      </style>
    </head>
    <body>
     <div id="app">${content}</div>
    </body>
  </html>
  `;

  if (!filePath) {
    fs.writeFileSync(`${process.cwd()}/${packageJson.name}.html`, data);
    return;
  }

  const fileHTML = fs.readFileSync(filePath);
  let $ = cheerio.load(fileHTML, { decodeEntities: false });
  $(injectSelector).html(content);
  fs.writeFileSync(filePath, $.html("html"));
}

module.exports = {
  init,
  createSkeleton
};
